#include <QMetaObject>
#include <QTimer>
#include <QTextStream>
#include <QFile>
#include <QDir>
#include <QTextCodec>
#include "QyConsoleApplication.h"
#include "QyDebug.h"

/**************************************************************************************************/

inline QString errorText( int error ) {

	switch (error) {

	case QProcess::FailedToStart:
		return "Failed To Start: The process failed to start. Either the invoked program is "
			"missing, or you may have insufficient permissions to invoke the program.";

	case QProcess::Crashed:
		return "Crashed: The process crashed some time after starting successfully.";

	case QProcess::Timedout:
		return "Timedout: The last waitFor...() function timed out. The state of QProcess"
			" is unchanged, and you can try calling waitFor...() again.";

	case QProcess::WriteError:
		return "Write Error: An error occurred when attempting to write to the process. "
			"For example, the process may not be running, or it may have closed its input channel.";

	case QProcess::ReadError:
		return "Read Error: An error occurred when attempting to read from the process. "
			"For example, the process may not be running.";

	case QProcess::UnknownError:
		return "UnknownError: An unknown error occurred. This is the default return value of "
			"error().";

	}

	return QString();
}

inline QString normalizeOutputString( const QByteArray & bytes ) {

	QString text;

#if defined(Q_OS_WIN32)
	const char codecName [] = "cp866";
#endif
// #elif defined(Q_OS_LINUX)
#if defined(Q_OS_LINUX)
// 	const char codecName [] = "utf-8";
	const char codecName [] = "System";
#endif

	if ( QTextCodec * codec = QTextCodec::codecForName(codecName) )
		text = codec->toUnicode(bytes);

	return text.remove('\r');
}

/**************************************************************************************************/
bool QyConsoleApplication::envChanged = true;

QyConsoleApplication::QyConsoleApplication( QObject * parent )
	: QObject(parent)
{

	p_process = new QProcess(this);
	p_process->setObjectName("process");

	QMetaObject::connectSlotsByName(this);

}

QyConsoleApplication::~QyConsoleApplication() {

	// delete p_process;

}

QStringList QyConsoleApplication::environment() const {

	return p_process->environment();
}

QString QyConsoleApplication::workingDirectory() const {

	return p_process->workingDirectory();
}

QString QyConsoleApplication::applicationName() const {

	return p_appName;
}

QStringList QyConsoleApplication::applicationArguments() const {

	return p_appArgs;
}

bool QyConsoleApplication::isRunning() const {

	return p_process->state() != QProcess::NotRunning;
}

QString QyConsoleApplication::applicationExePath( const QString & appName ) {

	if ( QFile::exists(appName) )
		return appName;

	static QStringList binEntrys;
	static QStringList exts = QStringList() << ".exe" << ".bat" << ".com" << ".sh";

	if (envChanged) {

		binEntrys.clear();
		envChanged = false;
		QStringList paths = systemPath( process()->environment() );

// 		qDebug("Bin entrys:");
		// QyDbgValue(paths);

		foreach ( QString path, paths ) {

			QStringList entrys = QDir(path).entryList(QDir::Files|QDir::Executable);

// 			qDebug() << " " << path << " [" << entrys.count() << "]";
			foreach ( QString entry, entrys ) {

				binEntrys.append( path + "/" + entry );
				// qDebug() << "  " << path + "/" + entry;

			}

		}

	}

	QStringList currentBinEntrys = binEntrys;
	QString current = QDir::currentPath();

	foreach ( QString entry, QDir(current).entryList(QDir::Files|QDir::Executable) )
		currentBinEntrys.append( current + "/" + entry );

// 	QyDbgLocation();
// 	QyDbgValue(appName);

	foreach ( QString entry, currentBinEntrys ) {

// 		QyDbgValue(entry);

		if ( entry.endsWith( "/" + appName ) ) {

			return entry;
		} else {

			foreach ( QString ext, exts ) {

				if ( entry.endsWith( "/" + appName + ext ) )
					return entry;

			}

		}

	}

	return QString();
}

/*static */QString QyConsoleApplication::envValue( const QString & name,
	const QStringList & senv )
{

	QStringList env(senv);

	if ( env.isEmpty() )
		env = QProcess::systemEnvironment();

	QString value, vName( name.toLower() + "=" );

	foreach ( QString envLine, env ) {

		if ( envLine.toLower().startsWith(vName) )
			return envLine.section( "=", 1 );

	}

	return QString();
}

/*static */QStringList QyConsoleApplication::systemPath( const QStringList & env ) {


	QString path = envValue( "path", env );

	return path.isEmpty() ? QStringList() : path.split(
#if defined(Q_OS_WIN32)
	";"
#endif
// #elif defined(Q_OS_LINUX)
#if defined(Q_OS_LINUX)
	":"
#endif
	);

}

/*static */QStringList QyConsoleApplication::normalizeEnvironment( const QStringList & env ) {

#ifdef Q_OS_WIN32
	QRegExp regExp("%(\\w+)%");
#else
	QRegExp regExp("\\$(\\w+)");
#endif//Q_OS_WIN32

	int currentValuePos = 0,
		offset = 0;
	QString variableName,
		cleanVariableName;
	QMap<QString,QString> envMap;
	QStringList splitedArg;
	QString name, value;
	bool complete = false;

	for ( int i = 0; i < 2; ++i ) {

		foreach ( QString oneEnv, env ) {

			splitedArg = oneEnv.split('=');

			if ( splitedArg.isEmpty() )
				continue;

			name = splitedArg.takeFirst().toUpper();
			value = splitedArg.join( "=" );
			offset = 0;

			while ( ( currentValuePos = regExp.indexIn( value, offset ) ) != -1 ) {

				variableName = regExp.cap();
				cleanVariableName = variableName;
#ifdef Q_OS_WIN32
				cleanVariableName.remove('%');
#else
				cleanVariableName.remove( 0, 1 );
#endif//Q_OS_WIN32

				if ( envMap.contains(cleanVariableName) ) {

// 					qDebug( "Replace %s -> %s", qPrintable(variableName),
// 						qPrintable(envMap[cleanVariableName]) );
					value.replace( currentValuePos, variableName.length(), envMap[cleanVariableName] );

				} else if (complete) {

// 					qDebug( "Kill unknow variable %s", qPrintable(variableName) );
					// unknowValues.append(variableName);
					value.replace( currentValuePos, variableName.length(), "" );

				} else {

					offset += currentValuePos + variableName.length();
// 					qDebug("Skip value");
// 					QyDbgValue(offset);

				}
// 				QyDbgValue(currentValuePos);
// 				QyDbgValue(offset);
// 				QyDbgValue(variableName);
// 				QyDbgValue(value);

			}

			envMap[name] = value;

		}

		if (!complete)
			complete = true;

	}

	QStringList okEnv;
	foreach ( variableName, envMap.keys() )
		okEnv.append( variableName + "=" + envMap[variableName] );

	return okEnv;
}

/*static */void QyConsoleApplication::setCurrentEnvironmentValue( const QString & name,
	const QString & value )
{

	QString envLine( name + "=" + value );

#ifdef Q_OS_WIN32

	putenv( envLine.toAscii().data() );

#else

	char * stupidEnvLine = new char[ envLine.length() + 1 ];
	strcpy( stupidEnvLine, qPrintable(envLine) );
	stupidEnvLine[ envLine.length() ] = '\0';
	putenv(stupidEnvLine);
	delete [] stupidEnvLine;
#endif//Q_OS_WIN32

}

/*static */void QyConsoleApplication::setCurrentEnvironment( const QStringList & environment ) {

	QString name;
	QStringList splitedArg;

	foreach ( QString oneEnv, environment ) {

		splitedArg = oneEnv.split('=');

		if ( splitedArg.isEmpty() )
			continue;

		name = splitedArg.takeFirst();
		setCurrentEnvironmentValue( name, splitedArg.join( "=" ) );

	}

// 	QyDbgLocation();
// 	QyDbgValue( "Global env\n  " + environment.join("\n  ") );
	envChanged = true;

}

void QyConsoleApplication::setEnvironment( const QStringList & env ) {

	p_process->setEnvironment( normalizeEnvironment(env) );
// 	QyDbgLocation();
// 	QyDbgValue( "Global env\n  " + p_process->environment().join("\n  ") );
	envChanged = true;

}

void QyConsoleApplication::setWorkingDirectory( const QString & dirPath ) {

	p_process->setWorkingDirectory(dirPath);

}

void QyConsoleApplication::setApplicationName( const QString & appName ) {

	p_appName = appName;

}

void QyConsoleApplication::setApplicationArguments( const QStringList & appArgs ) {

	p_appArgs = appArgs;

}

void QyConsoleApplication::start( const QString & appName, const QStringList & appArgs ) {

	// QyDbgLocation();
	p_appName = appName;
	p_appArgs = appArgs;

	run();

}

void QyConsoleApplication::run() {

// 	qDebug("-----------------------------");
	QyDbgLocation();
// 	QyDbgValue(p_appName);
// 	QyDbgValue( applicationExePath(p_appName) );
// 	QyDbgValue(p_appArgs);
// 	qDebug( "Environment:\n  %s", qPrintable( p_process->environment().join("\n  ") ) );

	QString appNameAbsPath = applicationExePath(p_appName);
	QyDbgLocation();
	QyDbgValue(appNameAbsPath);

	p_process->start( appNameAbsPath, p_appArgs );
	QyDbgLocation();

}

void QyConsoleApplication::stop() {

	if ( !isRunning() )
		return;

	p_process->terminate();
	QTimer::singleShot( 200, p_process, SLOT( kill() ) );

}

void QyConsoleApplication::kill() {

	p_process->kill();

}

void QyConsoleApplication::writeLine( const QString & str ) {

	QTextStream(p_process) << str << endl;

}

void QyConsoleApplication::processStarted() {

	emit started();

}

void QyConsoleApplication::processFinished( int exitCode ) {

	emit finished(exitCode);

}

void QyConsoleApplication::processReadRequest( const QString & prompt ) {

	emit readRequest(prompt);

}

void QyConsoleApplication::processStandartErrorMessage( const QString & text ) {

	emit standartErrorMessage(text);

}

void QyConsoleApplication::processStandartOutputMessage( const QString & text ) {

	emit standartOutputMessage(text);

}

void QyConsoleApplication::processErrorMessage( const QString & text ) {

	QyDbgLocation();
	emit errorMessage(text);

}

void QyConsoleApplication::on_process_error( QProcess::ProcessError error ) {

	QyDbgLocation();
	processErrorMessage( errorText(error) );
	emit finished(-1);

}

void QyConsoleApplication::on_process_finished( int exitCode, QProcess::ExitStatus exitStatus ) {

	Q_UNUSED(exitStatus);

	QyDbgLocation();
	processFinished(exitCode);

}

void QyConsoleApplication::on_process_readyReadStandardError() {

	QyDbgLocation();
	QStringList lines = normalizeOutputString( p_process->readAllStandardError() ).split("\n");

	foreach ( QString line, lines )
		processStandartErrorMessage(line);

}

void QyConsoleApplication::on_process_readyReadStandardOutput() {

	QyDbgLocation();
	QStringList lines = normalizeOutputString( p_process->readAllStandardOutput() ).split("\n");

	if ( lines.isEmpty() )
		return;

	foreach ( QString line, lines )
		processStandartOutputMessage(line);

	QString lastStdOutMessage;
	do {
		lastStdOutMessage = lines.takeLast();
	} while( lastStdOutMessage.simplified().isEmpty() && lines.count() );

	if ( lastStdOutMessage.simplified().count() )
		p_lastStdOutMessage = lastStdOutMessage;

}

void QyConsoleApplication::on_process_started() {

	QyDbgLocation();
	processStarted();

}

void QyConsoleApplication::on_process_stateChanged( QProcess::ProcessState newState ) {

	Q_UNUSED(newState);
	QyDbgLocation();

}

void QyConsoleApplication::on_process_aboutToClose() {

	QyDbgLocation();

}

void QyConsoleApplication::on_process_bytesWritten( qint64 bytes ) {

	QyDbgLocation();
	Q_UNUSED(bytes);

}

void QyConsoleApplication::on_process_readyRead() {

	QyDbgLocation();
	processReadRequest(p_lastStdOutMessage);

}
